package oboe.controllers;

import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.geom.Matrix;
import flash.utils.Dictionary;

import oboe.core.Oboe;
import oboe.math.Mesh3D;
import oboe.math.Number2D;
import oboe.math.Number3D;

import flash.Vector;

class GridController extends NodeController
{
private var textureGridSize:Int;
private var textureTileSize:Int;

private var gridSize:Float2D;
private var uv:Vector<Float>;
private var mesh:Vector<Number3D>;

public function new( heightMap:Vector<Float>, tiles:Float2D, tileSize:Float2D, textureCount:Int, textureSize:Int, smooth:Bool=false )
{
	tileSize.mul( Oboe.flags["SCALE_3D"] );
	var texture:BitmapData = prepareTexture( textureCount, textureSize );
	super( prepareMesh( heightMap, tiles, tileSize, smooth ) );
	this.renderSettings.texture = texture;
}

private static inline var tmp3:Float3D = new Number3D(0,0,0);
public override function slot_onGet3DPosition( p:Float3D ):Void
{
	tmp3.y = 0;
	tmp3.x = ((2*p.y-p.x)/2);
	tmp3.z =(p.x+tmp3.x);
	tmp3.add3( this.renderSettings.position );
	this.getGroundHeightAt( tmp3 );
	p.x = tmp3.x+tmp3.y;
	p.z = tmp3.z+tmp3.y;
	p.y = 0;
}

private static inline var tmatrix:Matrix = new Matrix();
private function setTexture( texture:DisplayObject, id:Int ):Void
{
	var w:Float = (this.renderSettings.texture.width-1)/this.textureGridSize;
	tmatrix.identity();
	var ty:Int = int( id/this.textureGridSize );
	var tx:Int = id-ty*this.textureGridSize;
	tmatrix.ty = ty*this.textureTileSize;
	tmatrix.tx = tx*this.textureTileSize;
	tmatrix.a  = this.textureTileSize/texture.width;
	tmatrix.d  = this.textureTileSize/texture.height;
	this.renderSettings.texture.draw( texture, tmatrix, null, null, null, true );
}

public function getTilePosition( tileX:Int, tileY:Int ):Float3D
{
	var r:Float3D = new Number3D( 
	tileX*this.renderSettings.scale.x+this.renderSettings.position.x + 0.5,
	0,
	tileY*this.renderSettings.scale.z+this.renderSettings.position.z + 0.5
	);
	
	r.y = this.mesh[ ((tileY)*this.gridSize.x+tileX)*6 ].y
	+ this.mesh[ ((tileY)*this.gridSize.x+tileX+1)*6 ].y
	+ this.mesh[ ((tileY+1)*this.gridSize.x+tileX+1)*6 ].y
	+ this.mesh[ ((tileY+1)*this.gridSize.x+tileX)*6 ].y;
	
	r.y = r.y*0.25*this.renderSettings.scale.y+this.renderSettings.position.y;
	
	this.renderSettings.rotator.apply( r );
	
	return r;
}

public function getTileHeightQuick( tileX:Int, tileY:Int ):Float
{
	var w:Int = this.gridSize.x;
	var ry:Float;
	ry  = this.mesh[ (tileY*w+tileX)*6 ].y
	+ this.mesh[ (tileY*w+tileX)*6+1 ].y
	+ this.mesh[ (tileY*w+tileX)*6+2 ].y
	+ this.mesh[ (tileY*w+tileX)*6+5 ].y;
	
	ry *= 0.25;
	
	return ry;
}


private static inline var tmp:Float3D = new Number3D(0,0,0);
private static inline var tmp2:Float3D=new Number3D(0,0,0);
private function getGroundHeightAt( pos:Float3D ):Void
{
	var base:Float3D = tmp2;
	var weight:Float3D = tmp;
	var sX:Int = this.gridSize.x;
	var sY:Int = this.gridSize.y;
	weight.copy3( pos ).sub3( this.renderSettings.position ).div3( this.renderSettings.scale );
	base.copy3( weight ).floor();
	weight.sub3( base );
	
	if( base.z >= sY-1 || base.z < 0 || base.x >= sX-1 || base.x < 0 || weight.x < 0 || weight.z < 0 )
	{
	pos.y = -60;
	return;
	}
	
	pos.y = this.mesh[ (base.z*sX+base.x)*6 ].y*(1-weight.x)*(1-weight.z) +
	this.mesh[ (base.z*sX+base.x)*6+2 ].y*(1-weight.x)*(weight.z) +
	this.mesh[ (base.z*sX+base.x)*6+1 ].y*(weight.x)*(1-weight.z) +
	this.mesh[ (base.z*sX+base.x)*6+5 ].y*(weight.x)*(weight.z);
	
	pos.y *= this.renderSettings.scale.y;
	pos.y += this.renderSettings.position.y;
	// trace( "px:", pos.x, "pz:", pos.z, "baseX:", base.x, "baseZ:", base.z, "y:", pos.y );
}

private function setTileTexture( tileX:Int, tileY:Int, id:Int ):Void
{
	var tyb:Float = int( id/this.textureGridSize );
	var txb:Float = id-tyb*this.textureGridSize;
	var tye:Float = tyb+1;
	var txe:Float = txb+1;
	var v:Int = (tileY*(this.gridSize.x) + tileX)*6*2;
	
	tyb /= this.textureGridSize;
	txb /= this.textureGridSize;
	tye /= this.textureGridSize;
	txe /= this.textureGridSize;
	
	this.uv[ v++ ]=txb; this.uv[ v++ ]=tyb;
	this.uv[ v++ ]=txe; this.uv[ v++ ]=tyb;
	this.uv[ v++ ]=txb; this.uv[ v++ ]=tye;
	this.uv[ v++ ]=txb; this.uv[ v++ ]=tye;
	this.uv[ v++ ]=txe; this.uv[ v++ ]=tyb;
	this.uv[ v++ ]=txe; this.uv[ v++ ]=tye;
}

private function prepareMesh( heightMap:Vector<Float>, tiles:Float2D, tileSize:Float2D, smooth:Bool ):Mesh3D
{
	tiles.floor().flip();
	this.gridSize = new Number2D( tiles.x, tiles.y );
	var frames:Vector< Vector.<Number3D> > = new Vector<Vector.<Number3D>>(1, true);
	this.uv = new Vector<Float>( (tiles.x)*(tiles.y)*6*2, true );
	this.mesh = new Vector<Number3D>( (tiles.x)*(tiles.y)*6, true );
	frames[0] = mesh;
	var y:Int;
	var x:Int;
	var w:Int = tiles.x;
	var h:Int = tiles.y;
	var i:Int = 0;
	var m:Float = 0;
	var tm:Float = 1/this.textureGridSize;
	
	for( y = 0; y < h; ++y )
	{
	for( x = 0; x < w; ++x )
	{
		mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x ], (x)*tileSize.x );
		mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x+1 ], (x+1)*tileSize.x );
		mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x ], (x)*tileSize.x );
		mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x ], (x)*tileSize.x );
		mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x+1 ], (x+1)*tileSize.x );
		mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x+1 ], (x+1)*tileSize.x );
	}
	}
	
	return new Mesh3D( null, frames, uv );
}

private function prepareTexture( textureCount:Int, textureSize:Int ):BitmapData
{
	this.textureGridSize = 1;
	while( this.textureGridSize*this.textureGridSize < textureCount )
	{
	++this.textureGridSize;
	}
	
	this.textureTileSize = textureSize;
	return new BitmapData( 
	textureSize*this.textureGridSize, 
	textureSize*this.textureGridSize,
	false,
	0x00A050
	);
}
}